
use std::collections::HashMap;
use std::vec::Vec;

#[derive(PartialEq, Debug)]
pub enum CompareSympol {
    GT,
    EQ,
    LT,
}

type PriorityUnit = i8;

#[derive(Debug)]
pub struct Operator {
    ops: Vec<char>,
    priority: HashMap<PriorityUnit, Vec<char>>,
}

impl Operator {
    pub fn new() -> Operator {
        let mut priority: HashMap<PriorityUnit, Vec<char>> = HashMap::new();
        priority.insert(0, vec!['(', ')']);
        priority.insert(1, vec!['*', '/']);
        priority.insert(2, vec!['+', '-']);
        Operator {
            ops: append_hash_values(&priority),
            priority: priority,
        }
    }

    pub fn get_ops(&self) -> &Vec<char> {
        &self.ops
    }

    pub fn compare_priority(&self, sympol_left: &char, sympol_right: &char) -> Result<CompareSympol, String>{
        let left_priority = self.get_priority(sympol_left);
        let right_priority = self.get_priority(sympol_right);
        if left_priority == None || right_priority == None {
            return Err(String::from("compare sympol is unsupport"));
        }
        let left_priority = left_priority.unwrap();
        let right_priority = right_priority.unwrap();
        let r: i16 = (right_priority - left_priority) as i16;
        if r == 0 {
            return Ok(CompareSympol::EQ);
        } else if r > 0 {
            return Ok(CompareSympol::GT);
        } else {
            return Ok(CompareSympol::LT);
        }
    }

    pub fn get_priority(&self, sympol: &char) -> Option<i8> {
        for (key, value) in &self.priority {
            if value.contains(sympol) {
                return Some(*key);
            }
        }
        None
    }

    pub fn has(&self, sympol: &char) -> bool {
        self.ops.iter().find(|&x| x == sympol) != None
    }

    pub fn operate(&self, theta: &char, left: &f64, right: &f64) -> Result<f64, String> {
        match theta {
            '+' => Ok(left + right),
            '-' => Ok(left - right),
            '*' => Ok(left * right),
            '/' => {
                if *right != 0.0 {
                    Ok(left / right)
                } else {
                    Err("Divisor cannot be zero".into())
                }
            },
            _ => Err(format!("Theta({}) is not support", theta)),
        }
    }
}

fn append_hash_values(p: &HashMap<PriorityUnit, Vec<char>>) -> Vec<char> {
    let mut ops: Vec<char> = Vec::new();
    let priority_values = p.values();
    for priority_value in priority_values {
        for priority_char in priority_value {
            ops.push(*priority_char);
        }
    }
    ops
}



#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn ops_len() {
        let operator = Operator::new();
        assert_eq!(6, operator.get_ops().len());
    }

    #[test]
    fn append_hash_values_works() {
        let priority_one: Vec<char> = vec!['(', ')'];
        let priority_two: Vec<char> = vec!['*', '/'];
        let priority_three: Vec<char> = vec!['+', '-'];
        let mut priority: HashMap<PriorityUnit, Vec<char>> = HashMap::new();
        priority.insert(1, priority_one);
        priority.insert(2, priority_two);
        priority.insert(3, priority_three);
        let ops = append_hash_values(&priority);
        let priority_all: Vec<char> = vec!['+', '-', '(', ')', '*', '/'];
        println!("{:#?}", ops);
        assert_eq!(ops.len(), priority_all.len());
        for x in priority_all {
            assert_eq!(true, ops.contains(&x));
        }
    }
    #[test]
    fn get_priority_works() {
        let operator = Operator::new();
        assert_eq!(operator.get_priority(&'+'), Some(2));
        assert_eq!(operator.get_priority(&'*'), Some(1));
        assert_eq!(operator.get_priority(&'&'), None);
    }

    #[test]
    fn compare_priority_works() {
        let operator = Operator::new();
        
        assert_eq!(CompareSympol::EQ, operator.compare_priority(&'+', &'-').unwrap());
        assert_eq!(CompareSympol::EQ, operator.compare_priority(&'+', &'+').unwrap());
        assert_eq!(CompareSympol::EQ, operator.compare_priority(&'-', &'-').unwrap());
        assert_eq!(CompareSympol::EQ, operator.compare_priority(&'*', &'*').unwrap());
        assert_eq!(CompareSympol::EQ, operator.compare_priority(&'/', &'/').unwrap());
        assert_eq!(CompareSympol::GT, operator.compare_priority(&'*', &'-').unwrap());
        assert_eq!(CompareSympol::GT, operator.compare_priority(&'(', &'-').unwrap());
        assert_eq!(CompareSympol::GT, operator.compare_priority(&'(', &'*').unwrap());
        assert_eq!(CompareSympol::GT, operator.compare_priority(&'(', &'*').unwrap());
        assert_eq!(CompareSympol::EQ, operator.compare_priority(&'(', &'(').unwrap());
        assert_eq!(CompareSympol::EQ, operator.compare_priority(&'(', &')').unwrap());
        assert_eq!(CompareSympol::LT, operator.compare_priority(&'+', &'*').unwrap());
        assert_eq!(Err(String::from("compare sympol is unsupport")), operator.compare_priority(&'^', &')'));
        assert_eq!(Err(String::from("compare sympol is unsupport")), operator.compare_priority(&'/', &'%'));
        assert_eq!(Err(String::from("compare sympol is unsupport")), operator.compare_priority(&'=', &'%'));
    }

    #[test]
    fn has_works() {
        let operator = Operator::new();
        assert!(operator.has(&'+'));
        assert!(operator.has(&'-'));
        assert!(operator.has(&'*'));
        assert!(operator.has(&'/'));
        assert!(operator.has(&'('));
        assert!(operator.has(&')'));
        assert!(!operator.has(&'&'));
    }

    #[test]
    fn operate_works() {
        let operator = Operator::new();
        assert_eq!(operator.operate(&'+', &2.0_f64, &3.0_f64).unwrap(), 5.0_f64);
        assert_eq!(operator.operate(&'-', &3.0_f64, &2.0_f64).unwrap(), 1.0_f64);
        assert_eq!(operator.operate(&'*', &3.0_f64, &2.0_f64).unwrap(), 6.0_f64);
        assert_eq!(operator.operate(&'/', &4.0_f64, &2.0_f64).unwrap(), 2.0_f64);
        assert_eq!(operator.operate(&'/', &3.0_f64, &2.0_f64).unwrap(), 1.5_f64);
        let result = operator.operate(&'^', &3.0_f64, &2.0_f64);
        match result {
            Ok(_x) => {
                assert!(false);
            },
            Err(s) => {
                assert_eq!(String::from("Theta(^) is not support"), s);
            },
        }
        let result = operator.operate(&'/', &3.0_f64, &0.0);
        match result {
            Ok(x) => {
                assert!(false);
            },
            Err(s) => {
                assert_eq!(String::from("Divisor cannot be zero"), s);
            },
        }
    }

    #[test]
    fn test_get_ops() {
        let operator = Operator::new();
        let ops = operator.get_ops();
        let mut expect = vec!['+', '-', '*', '/', '(', ')'];
        assert_eq!(expect.len(), ops.len());
        assert_eq!(expect.sort(), ops.clone().sort());
    }
}